<?php
class Core_Model_App {

	const CONFIG_MODULE     = 'modules';

	const CONFIG_CONNECTION = 'connection';

	const CONFIG_ROUTER     = 'routers';

	const CONFIG_REWRITE    = 'rewrites';

	/**
	 * Application configuration object
	 *
	 * @var Core_Model_Config
	 */
	protected $_config;

	/**
	 * Database connection
	 *
	 * @var Core_Model_Db_Connection
	 */
	protected $_connection = null;

	/**
	 * Router objects
	 *
	 * @var Core_Model_Router_Abstract
	 */
	protected $_routers = array();

	/**
	 * Available modules
	 *
	 * @var Core_Model_Module
	 */
	protected $_modules = array();

    /**
     * Constructor
     */
	public function __construct() {	}
	
	/**
	 * Run application. Run process responsible for request processing and sending response.
	 *
	 * @return Core_Model_App
	 */
	public function run() {
        //Varien_Profiler::start('app/run');
		Mag::registerProvider($this);
		try {
			$this->_initConfig();
			$this->_initModules();
			$this->_initRouters();
			$this->_dispatch();
		} catch (Exception $e) {
			Mag::removeProvider($this);
			throw $e;
		}
		Mag::removeProvider($this);
        //Varien_Profiler::stop('app/run');
		return $this;
	}

	/**
	 * Load application configuration
	 *
	 * @return Core_Model_App
	 */
	protected function _initConfig() {
        //Varien_Profiler::start('app/load-config');
		$this->_config = new Core_Model_Config();
		
		// load base config
		$this->_config->loadFile(Mag::getCodeDir() . DS . 'config.php');
		
		// load module config
		$this->_loadModuleConfig($modulesInfo, $modulesConfig);
		$this->_loadDbConfig($modulesInfoDb, $dbConfig);

		// sort modules list
		$this->_config->merge(array(self::CONFIG_MODULE => array_merge($modulesInfo, $modulesInfoDb)));
		$this->_config->sortConfig(self::CONFIG_MODULE, 'order');
		
		// merge modules config to global config
		foreach ($this->_config->get(self::CONFIG_MODULE) as $name => $info) {
			$this->_modules[$name] = new Core_Model_Module($info);
			if (!$info['active']) continue;
			$this->_config->merge($modulesConfig[$name], true);
		}
		
		// merge db config to global config
		$this->_config->merge($dbConfig);
        //Varien_Profiler::end('app/load-config');
	}

	/**
	 * Load config from module's config file
	 *
	 * @return Core_Model_App
	 */
	protected function _loadModuleConfig(&$modulesInfo, &$modulesConfig) {
		$modulesInfo = array();
		$modulesConfig = array();
        //Varien_Profiler::start('app/load-modules-config');
		$modulesPath = glob(Mag::getCodeDir() . DS . '*', GLOB_ONLYDIR);
		foreach ($modulesPath as $modulePath) {
			$data = $this->_config->loadFile($modulePath . DS . 'config.php', false);
			$modulesInfo = array_merge($modulesInfo, $data[self::CONFIG_MODULE]);

			unset($data[self::CONFIG_MODULE]);
			$moduleName = basename($modulePath);
			$modulesConfig[$moduleName] = $data;
		}
        //Varien_Profiler::stop('app/load-modules-config');
		
		return $this;
	}

	/**
	 * Load config from database
	 *
	 * @return Core_Model_App
	 */
	protected function _loadDbConfig(&$modulesInfoDb, &$dbConfig) {
		$modulesInfoDb = array();
		$dbConfig = array();
		if (!Mag::isInstalled()) return $this;

        //Varien_Profiler::start('app/load-db-config');
		$data = $this->_config->loadDb($this->getConnection(), false);
		// if user had save something in module manager, apply that setting
		if (isset($data[self::CONFIG_MODULE])) {
			$modulesInfoDb = $data[self::CONFIG_MODULE];
			unset($data[self::CONFIG_MODULE]);
		}
		$dbConfig = $data;
        //Varien_Profiler::stop('app/load-db-config');
		return $this;
	}

	/**
	 * Run all module setup scripts
	 *
	 * @return Core_Model_App
	 */
	protected function _initModules() {
		if (!Mag::isInstalled()) return $this;
		//Mag::trigger('app_modules_init_before', $this);
		//Varien_Profiler::start('app/init-modules');
		
		// foreach modules -> rund module install
		
		//Varien_Profiler::stop('app/init-modules');
		//Mag::trigger('app_modules_init_after', $this);
	}
	
	protected function _initRouters() {
		//Mag::trigger('app_routers_init_before', $this);
		//Varien_Profiler::start('app/init-routers');

		$routersInfo = $this->getConfig()->get(self::CONFIG_ROUTER);
		foreach($routersInfo as $name => $config) {
			if (!$config['active']) continue;
			$router = Mag::singleton($config['class'], $config);
			$this->addRouter($name, $router);
		}

		//Varien_Profiler::stop('app/init-routers');
		//Mag::trigger('app_routers_init_after', $this);
	}
	
	protected function _dispatch() {
		//Mag::trigger('app_dispatch_before', $this);
		//Varien_Profiler::start('app/dispatch');

		$request = $this->getRequest();
		
		// rewrite request
		
		for($i=0; $i < 10 && !$request->isDispatched(); $i++) {
			foreach($this->_routers as $router) {
				if ($request->isDispatched()) break;

				Mag::registerProvider($router);
				try {
					$router->process($request);
				} catch (Exception $e) {
					Mag::removeProvider($router);
					throw $e;
				}
				Mag::removeProvider($router);
			}
		}

		//Varien_Profiler::start('app/dispatch');
		//Mag::trigger('app_dispatch_after', $this);
		
		if (!$request->isDispatched()) throw new Exception('No router match this request');
		
        //Varien_Profiler::start('app/send-response');
		//Mag::trigger('app_response_send_before', $this);
		$this->getResponse()->send();
		//Mag::trigger('app_response_send_after', $this);
        //Varien_Profiler::stop('app/send-response');
		return $this;
	}
	
	public function addRouter($name, $class) {
		$this->_routers[$name] = $class;
	}

	/**
	 * Get object class instance.
	 *
	 * @param string $className
	 * @param array|object $constructArguments
	 * @return mixed
	 */
	public function getOjectInstance() {
		$args = func_get_args();
		$className = $this->_getObjectClassName( array_shift($args) );
		if (class_exists($className)) {
			$class = new ReflectionClass($className);
			$instance = $class->newInstanceArgs($args);
			return $instance;
		} else {
			return null;
		}
	}

	/**
	 * Retrieve rewritten class name
	 *
	 * @param   string $className
	 * @return  string
	 */
	protected function _getObjectClassName($className) {
		// apply class rewrite setted in config
		$rewrites = $this->_config->get(self::CONFIG_REWRITE);
		while (isset($rewrites[$className])) {
			$className = $rewrites[$className];
		}
		return $className;
	}

	public function getConfig() {
		return $this->_config;
	}

	public function getModules() {
		return $this->_modules;
	}

	public function getConnection() {
		if (!$this->_connection) {
			$connConfig = $this->getConfig()->get(self::CONFIG_CONNECTION);
			$this->_connection = Mag::object('Core_Model_Db_Connection', $connConfig);
		}
		return $this->_connection;
	}
	
	public function getRequest() {
		return Mag::singleton('Core_Model_Request');
	}

	public function getResponse() {
		return Mag::singleton('Core_Model_Response');
	}

	public function getUrl($routePath = null, $routeParams = null) {
		return Mag::object('Core_Model_Url')->getUrl($routePath, $routeParams);
	}

}